// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package dwarfgen

import (
	"fmt"
	"strings"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/internal/dwarf"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// 根据原始源位置识别变量。
type varPos struct {
	DeclName string
	DeclFile string
	DeclLine uint
	DeclCol  uint
}

// 这是收集原材料的主要切入点，以推动DWARF“内联子程序”模具的生成。有关更多详细信息和背景信息，请参见提案
// 22080。
func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls {
	var inlcalls dwarf.InlCalls

	if base.Debug.DwarfInl != 0 {
		base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name)
	}

	// 这将内联索引（从Ctxt.InlTree）映射到inlcalls中的索引。调用
	imap := make(map[int]int)

	// Walk progs以建立InlCalls数据结构
	var prevpos src.XPos
	for p := fnsym.Func().Text; p != nil; p = p.Link {
		if p.Pos == prevpos {
			continue
		}
		ii := posInlIndex(p.Pos)
		if ii >= 0 {
			insertInlCall(&inlcalls, ii, imap)
		}
		prevpos = p.Pos
	}

	// 这用于按内联索引对矮化变量进行分区。内联线生成的非
	// 变量将在vmap[0]条目中结束。
	vmap := make(map[int32][]*dwarf.Var)

	// 现在遍历dwarf变量，并根据它们是由内联线生成的（dwv.InlIndex>0）还是来自函数（dwv.InlIndex==0）的原始
	// 变量/参数对它们进行分区。
	for _, dwv := range dwVars {

		vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv)

		// Zero index=>var不是由内联的
		if dwv.InlIndex == 0 {
			continue
		}

		// 在我们的映射中查找索引生成的，然后将有问题的var 
		// 添加到vars列表中，以获得正确的内联调用。
		ii := int(dwv.InlIndex) - 1
		idx, ok := imap[ii]
		if !ok {
			// 我们偶尔会遇到由
			// 内联生成的var，其中没有剩余的程序；在这种情况下，在通话列表中添加一个新的
			// 条目。
			idx = insertInlCall(&inlcalls, ii, imap)
		}
		inlcalls.Calls[idx].InlVars =
			append(inlcalls.Calls[idx].InlVars, dwv)
	}

	// 对上面的映射进行后期处理，以将子索引分配给变量。
	// 
	// 根据给定变量是顶级函数的一部分（ii==0）还是内联函数（ii！=0）产生的
	// 来区别对待。
	// 
	// 如果一个变量不是由内联函数生成的，并且其包含的
	// 函数不是内联函数，那么我们只需根据变量名指定
	// 的顺序。
	// 
	// 如果变量不是由内联生成的，并且其包含的
	// 函数是内联的，然后我们需要根据抽象函数中变量的顺序分配一个子索引
	// 在
	// 中，那些没有出现在抽象
	// 函数中的变量，例如“~r1”，被标记为这样的。
	// 
	// 如果一个变量是由一个内联函数生成的，那么我们在
	// 目标函数的预内联decls中找到它，并相应地分配子
	// 索引。
	for ii, sl := range vmap {
		var m map[varPos]int
		if ii == 0 {
			if !fnsym.WasInlined() {
				for j, v := range sl {
					v.ChildIndex = int32(j)
				}
				continue
			}
			m = makePreinlineDclMap(fnsym)
		} else {
			ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1))
			m = makePreinlineDclMap(ifnlsym)
		}

		// 这里我们根据
		// 预内联decls为变量分配子索引，并适当设置“IsInAbstract”标志
		// 。此外：参数和局部变量
		// 名称被赋予“中间点”版本号，作为
		// 输出数据的一部分（见第4326期）。如果启用了DWARF 
		// 内联例程生成，我们希望撤消
		// 此版本控制，因为所讨论的DWARF变量将是
		// 内联例程的父对象，而不是顶级
		// 调用方。
		synthCount := len(m)
		for _, v := range sl {
			canonName := unversion(v.Name)
			vp := varPos{
				DeclName: canonName,
				DeclFile: v.DeclFile,
				DeclLine: v.DeclLine,
				DeclCol:  v.DeclCol,
			}
			synthesized := strings.HasPrefix(v.Name, "~r") || canonName == "_" || strings.HasPrefix(v.Name, "~b")
			if idx, found := m[vp]; found {
				v.ChildIndex = int32(idx)
				v.IsInAbstract = !synthesized
				v.Name = canonName
			} else {
				// 在预内联dcl列表中找不到变量。
				// 在顶级情况下（ii=0），这种情况可能发生
				// 因为一个复合变量被拆分为多个部分，
				// 我们正在查看一个部分。我们还可以看到
				// 返回在
				// 降低期间创建的临时值（~r%d），或未命名的参数（“”）。
				v.ChildIndex = int32(synthCount)
				synthCount++
			}
		}
	}

	// 第二次遍历程序，计算各种内联调用的PC范围。
	start := int64(-1)
	curii := -1
	var prevp *obj.Prog
	for p := fnsym.Func().Text; p != nil; prevp, p = p, p.Link {
		if prevp != nil && p.Pos == prevp.Pos {
			continue
		}
		ii := posInlIndex(p.Pos)
		if ii == curii {
			continue
		}
		// 结束当前范围
		if start != -1 {
			addRange(inlcalls.Calls, start, p.Pc, curii, imap)
		}
		// 开始新范围
		start = p.Pc
		curii = ii
	}
	if start != -1 {
		addRange(inlcalls.Calls, start, fnsym.Size, curii, imap)
	}

	// 问题33188：如果II foo是II bar的子项，那么确保
	// bar的范围包括foo的范围（上面的循环将产生
	// 不相交的范围）。
	for k, c := range inlcalls.Calls {
		if c.Root {
			unifyCallRanges(inlcalls, k)
		}
	}

	// 调试
	if base.Debug.DwarfInl != 0 {
		dumpInlCalls(inlcalls)
		dumpInlVars(dwVars)
	}

	// 对上述unifyCallRanges生成的内联常规PC范围
	// 执行一致性检查。特别是，在A->B->C（例如C内联到B中，而
	// B内联到A中）和B的范围不在A的范围内或B的范围内的情况下，投诉
	// 。在编译的后期，当我们确定需要一个
	for k, c := range inlcalls.Calls {
		if c.Root {
			checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1)
		}
	}

	return inlcalls
}

// 抽象函数DIE。
// 抽象函数DIE用于从之前编译的
// 包导入的内联例程时，这个函数被称为
func AbstractFunc(fn *obj.LSym) {
	ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn)
	if ifn == nil {
		base.Ctxt.Diag("failed to locate precursor fn for %v", fn)
		return
	}
	_ = ifn.(*ir.Func)
	if base.Debug.DwarfInl != 0 {
		base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name)
	}
	base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath)
}

// 撤消将名称写入导出数据时执行的任何版本控制
// 导出数据。
func unversion(name string) string {
	if i := strings.Index(name, "·"); i > 0 {
		name = name[:i]
	}
	return name
}

// 给定一个作为编译一部分内联的函数，挖掘
// 为该函数建立预内联DCL列表，并创建一个映射，
// 支持基于变量
// position/name查找预内联DCL索引。注意：计算变量pos/file/line 
// 的方法需要与gc中的类似代码保持同步。createSimpleVars 
// 和相关函数。
func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int {
	dcl := preInliningDcls(fnsym)
	m := make(map[varPos]int)
	for i, n := range dcl {
		pos := base.Ctxt.InnermostPos(n.Pos())
		vp := varPos{
			DeclName: unversion(n.Sym().Name),
			DeclFile: pos.RelFilename(),
			DeclLine: pos.RelLine(),
			DeclCol:  pos.RelCol(),
		}
		if _, found := m[vp]; found {
			// 我们可以在模糊处理或机器生成的代码中看到冲突（具有相同名称/文件/行/列的变量）——请参阅问题44378以获取示例。在这种情况下跳过重复的代码，因为人类不太可能调试这样的代码。
			continue
		}
		m[vp] = i
	}
	return m
}

func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
	callIdx, found := imap[inlIdx]
	if found {
		return callIdx
	}

	// 还没有看到这一点。如果有
	// 的话，请访问inline的父级。我们首先这样做是为了让父母出现在结果表中他们的
	// 孩子之前。
	parCallIdx := -1
	parInlIdx := base.Ctxt.InlTree.Parent(inlIdx)
	if parInlIdx >= 0 {
		parCallIdx = insertInlCall(dwcalls, parInlIdx, imap)
	}

	// 为此内联创建新条目
	inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx)
	callXPos := base.Ctxt.InlTree.CallPos(inlIdx)
	absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn)
	pb := base.Ctxt.PosTable.Pos(callXPos).Base()
	callFileSym := base.Ctxt.Lookup(pb.SymFilename())
	ic := dwarf.InlCall{
		InlIndex:  inlIdx,
		CallFile:  callFileSym,
		CallLine:  uint32(callXPos.Line()),
		AbsFunSym: absFnSym,
		Root:      parCallIdx == -1,
	}
	dwcalls.Calls = append(dwcalls.Calls, ic)
	callIdx = len(dwcalls.Calls) - 1
	imap[inlIdx] = callIdx

	if parCallIdx != -1 {
		// 将此内联添加到父级的子列表
		dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx)
	}

	return callIdx
}

// 给定src。XPos，如果
// 对应于内联创建的内容，则返回其关联的内联索引；如果
// 没有内联信息，则返回-1。请注意，返回的索引将引用
// 内联堆栈中最深的调用，例如，如果您有“A调用B 
// 调用C调用D”，并且所有三个被调用方都是内联的（B、C和D），
// 来自D内联体的节点索引将引用
// 来自C的调用。Whew。
func posInlIndex(xpos src.XPos) int {
	pos := base.Ctxt.PosTable.Pos(xpos)
	if b := pos.Base(); b != nil {
		ii := b.InliningIndex()
		if ii >= 0 {
			return ii
		}
	}
	return -1
}

func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) {
	if start == -1 {
		panic("bad range start")
	}
	if end == -1 {
		panic("bad range end")
	}
	if ii == -1 {
		return
	}
	if start == end {
		return
	}
	// 将范围追加到正确的内联调用
	callIdx, found := imap[ii]
	if !found {
		base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start)
	}
	call := &calls[callIdx]
	call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end})
}

func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) {
	for i := 0; i < ilevel; i++ {
		base.Ctxt.Logf("  ")
	}
	ic := inlcalls.Calls[idx]
	callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex)
	base.Ctxt.Logf("  %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name)
	for _, f := range ic.InlVars {
		base.Ctxt.Logf(" %v", f.Name)
	}
	base.Ctxt.Logf(" ) C: (")
	for _, k := range ic.Children {
		base.Ctxt.Logf(" %v", k)
	}
	base.Ctxt.Logf(" ) R:")
	for _, r := range ic.Ranges {
		base.Ctxt.Logf(" [%d,%d)", r.Start, r.End)
	}
	base.Ctxt.Logf("\n")
	for _, k := range ic.Children {
		dumpInlCall(inlcalls, k, ilevel+1)
	}

}

func dumpInlCalls(inlcalls dwarf.InlCalls) {
	for k, c := range inlcalls.Calls {
		if c.Root {
			dumpInlCall(inlcalls, k, 0)
		}
	}
}

func dumpInlVars(dwvars []*dwarf.Var) {
	for i, dwv := range dwvars {
		typ := "local"
		if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM {
			typ = "param"
		}
		ia := 0
		if dwv.IsInAbstract {
			ia = 1
		}
		base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ)
	}
}

func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) {
	for _, r := range par {
		if rng.Start >= r.Start && rng.End <= r.End {
			return true, ""
		}
	}
	msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End)
	for _, r := range par {
		msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End)
	}
	msg += " }"
	return false, msg
}

func rangesContainsAll(parent, child []dwarf.Range) (bool, string) {
	for _, r := range child {
		c, m := rangesContains(parent, r)
		if !c {
			return false, m
		}
	}
	return true, ""
}

// checkInlCall验证内联信息'idx'的PC范围是否为
// 封闭/包含在其父内联的范围内（或者如果
// 这是根/顶级内联，则检查范围是否在
// 顶级函数的范围内）。如果发现
// 格式不正确的范围，将发出警报。
func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) {

	// 被调用方
	ic := inlCalls.Calls[idx]
	callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name
	calleeRanges := ic.Ranges

	// 调用方
	caller := funcName
	parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}}
	if parentIdx != -1 {
		pic := inlCalls.Calls[parentIdx]
		caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name
		parentRanges = pic.Ranges
	}

	// 调用方范围中包含的被调用方范围？
	c, m := rangesContainsAll(parentRanges, calleeRanges)
	if !c {
		base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m)
	}

	// 现在访问孩子们
	for _, k := range ic.Children {
		checkInlCall(funcName, inlCalls, funcSize, k, idx)
	}
}

// unifyCallRanges确保给定内联
// 可传递地包括其子内联的所有范围。
func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) {
	ic := &inlcalls.Calls[idx]
	for _, childIdx := range ic.Children {
		// 首先确保子范围是统一的。
		unifyCallRanges(inlcalls, childIdx)

		// 然后将子范围合并为此内联的范围。
		cic := inlcalls.Calls[childIdx]
		ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges)
	}
}
